﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using CoreModel = PlayerScannerCore.Model;
using Mvvm;
using System.Collections.ObjectModel;
using PlayerScannerCore.Model;

namespace PlayerScannerCore.ViewModel
{
    public class FloorViewModel : ViewModelBase
    {
        private CoreModel.Model _model;
        private ObservableCollection<ItemViewModel> _floorItems = new ObservableCollection<ItemViewModel>();
        private bool _showOnlyInterestingItems;
        private Dictionary<ItemType, List<FilterRule>> _filterRules = new Dictionary<ItemType, List<FilterRule>>();

        public FloorViewModel(CoreModel.Model model) 
        { 
            _model = model;

            ParseFilterRules(Properties.Settings.Default.FloorItemsFilterRules);
        }

        public ObservableCollection<ItemViewModel> FloorItems
        {
            get { return _floorItems; }
            set
            {
                if (_floorItems != value)
                {
                    _floorItems = value;
                    OnPropertyChanged("FloorItems");
                }
            }
        }

        public bool ShowOnlyInterestingItems
        {
            get { return _showOnlyInterestingItems; }
            set
            {
                if (_showOnlyInterestingItems != value)
                {
                    _showOnlyInterestingItems = value;
                    OnPropertyChanged("ShowOnlyInterestingItems");

                    //Refresh right away for a better user experience.
                    Refresh();
                }
            }
        }

        private bool IsInteresting(ItemViewModel item)
        {
            if (_filterRules.ContainsKey(item.ItemType))
            {
                foreach (var rule in _filterRules[item.ItemType])
                    if (rule.IsMatch(item)) return true;
            }

            return false;
        }

        /// <summary>
        /// Causes this view model and all child view models to update themselves.
        /// </summary>
        public void Refresh()
        {
            try
            {
                var floorItems = FloorItems;
                var newInventory = _model.GetFloorItems().ConvertAll<ItemViewModel>(item => (ItemViewModel)item);

                if (ShowOnlyInterestingItems)
                {
                    //Remove uninteresting items
                    var uninterestingItems = new List<ItemViewModel>();
                    foreach (var item in newInventory)
                    {
                        if (!IsInteresting(item))
                            uninterestingItems.Add(item);
                    }
                    foreach (var item in uninterestingItems)
                        newInventory.Remove(item);
                }

                //Remove old items by throwing out all current inventory items that aren't in the new 
                //collection of inventory items returned from the model. Update any items that are 
                //already in the current inventory.
                var itemsToRemove = new List<ItemViewModel>();
                foreach (var item in floorItems)
                {
                    var foundElement = newInventory.FirstOrDefault(element => element.Id == item.Id);
                    if (foundElement == null)
                        itemsToRemove.Add(item);
                    else
                        item.Update(foundElement);
                }
                foreach (var item in itemsToRemove)
                    floorItems.Remove(item);

                //Add to the current inventory any items that are in the inventory returned by the 
                //model but not in the current inventory.
                foreach (var item in newInventory)
                {
                    if (floorItems.FirstOrDefault(element => element.Id == item.Id) == null)
                        floorItems.Insert(0, item);
                }
            }catch (Exception) { } //Ignore all exceptions
        }

        #region Filter Helpers

        private void ParseFilterRules(string filterRules)
        {
            //Create filter rules from settings
            using (var stringReader = new StringReader(filterRules))
            {
                while (true)
                {
                    var line = stringReader.ReadLine();

                    //End of input is reached when ReadLine returns null
                    if (line == null)
                        break;

                    line = line.Trim().ToLower();

                    //Ignore comments
                    if (line.StartsWith("#"))
                        continue;

                    //Split the line into tokens delimited by a semi-colon
                    var tokens = line.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                    if (tokens.Length < 1)
                        continue;

                    //Helper variables used during decoding
                    bool boolParsed;
                    NumericDescriptor descriptorParsed;

                    //The first token should be the rule type
                    switch (tokens[0].Trim())
                    {
                    case "w":
                        var weapFilter = new WeaponFilterRule();
                        for (int i = 1; i < tokens.Length; i++)
                        {
                            var namedParam = GetNamedParam(tokens[i].Trim());
                            if (namedParam == null)
                                continue;

                            switch (namedParam.Item1)
                            {
                            case "i":
                                weapFilter.ItemName = namedParam.Item2;
                                break;
                            case "t":
                                if (!TryGetBoolValue(namedParam.Item2, out boolParsed))
                                    continue;

                                weapFilter.IsTekked = boolParsed;
                                break;
                            case "b":
                                if (!TryGetBoolValue(namedParam.Item2, out boolParsed))
                                    continue;

                                weapFilter.IsBasic = boolParsed;
                                break;
                            case "g":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                weapFilter.Grind = descriptorParsed;
                                break;
                            case "n":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                weapFilter.Native = descriptorParsed;
                                break;
                            case "a":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                weapFilter.ABeast = descriptorParsed;
                                break;
                            case "m":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                weapFilter.Machine = descriptorParsed;
                                break;
                            case "d":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                weapFilter.Dark = descriptorParsed;
                                break;
                            case "h":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                weapFilter.Hit = descriptorParsed;
                                break;
                            case "s":
                                weapFilter.Special = new WeaponSpecialDescriptor(namedParam.Item2);
                                break;
                            }
                        }
                        AddFilterToDictionary(ItemType.Weapon, weapFilter);
                        break;
                    case "k":
                        var sWeapFilter = new SWeaponFilterRule();
                        for (int i = 1; i < tokens.Length; i++)
                        {
                            var namedParam = GetNamedParam(tokens[i].Trim());
                            if (namedParam == null)
                                continue;

                            switch (namedParam.Item1)
                            {
                            case "i":
                                sWeapFilter.ItemName = namedParam.Item2;
                                break;
                            case "g":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                sWeapFilter.Grind = descriptorParsed;
                                break;
                            case "n":
                                sWeapFilter.CustomName = namedParam.Item2;
                                break;
                            case "s":
                                sWeapFilter.Special = new WeaponSpecialDescriptor(namedParam.Item2);
                                break;
                            }
                        }
                        AddFilterToDictionary(ItemType.SWeapon, sWeapFilter);
                        break;
                    case "a":
                        var armorFilter = new ArmorFilterRule();
                        for (int i = 1; i < tokens.Length; i++)
                        {
                            var namedParam = GetNamedParam(tokens[i].Trim());
                            if (namedParam == null)
                                continue;

                            switch (namedParam.Item1)
                            {
                            case "i":
                                armorFilter.ItemName = namedParam.Item2;
                                break;
                            case "d":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                armorFilter.Defense = descriptorParsed;
                                break;
                            case "e":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                armorFilter.Evasion = descriptorParsed;
                                break;
                            case "s":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                armorFilter.Slots = descriptorParsed;
                                break;
                            }
                        }
                        AddFilterToDictionary(ItemType.Armor, armorFilter);
                        break;
                    case "s":
                        var shieldFilter = new ShieldFilterRule();
                        for (int i = 1; i < tokens.Length; i++)
                        {
                            var namedParam = GetNamedParam(tokens[i].Trim());
                            if (namedParam == null)
                                continue;

                            switch (namedParam.Item1)
                            {
                            case "i":
                                shieldFilter.ItemName = namedParam.Item2;
                                break;
                            case "d":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                shieldFilter.Defense = descriptorParsed;
                                break;
                            case "e":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                shieldFilter.Evasion = descriptorParsed;
                                break;
                            }
                        }
                        AddFilterToDictionary(ItemType.Shield, shieldFilter);
                        break;
                    case "u":
                        var unitFilter = new FilterRule();
                        for (int i = 1; i < tokens.Length; i++)
                        {
                            var namedParam = GetNamedParam(tokens[i].Trim());
                            if (namedParam == null)
                                continue;

                            switch (namedParam.Item1)
                            {
                            case "i":
                                unitFilter.ItemName = namedParam.Item2;
                                break;
                            }
                        }
                        AddFilterToDictionary(ItemType.Unit, unitFilter);
                        break;
                    case "m":
                        var magFilter = new FilterRule();
                        for (int i = 1; i < tokens.Length; i++)
                        {
                            var namedParam = GetNamedParam(tokens[i].Trim());
                            if (namedParam == null)
                                continue;

                            switch (namedParam.Item1)
                            {
                            case "i":
                                magFilter.ItemName = namedParam.Item2;
                                break;
                            }
                        }
                        AddFilterToDictionary(ItemType.Mag, magFilter);
                        break;
                    case "t":
                        var techDiskFilter = new TechDiskFilterRule();
                        for (int i = 1; i < tokens.Length; i++)
                        {
                            var namedParam = GetNamedParam(tokens[i].Trim());
                            if (namedParam == null)
                                continue;

                            switch (namedParam.Item1)
                            {
                            case "i":
                                techDiskFilter.ItemName = namedParam.Item2;
                                break;
                            case "l":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                techDiskFilter.Level = descriptorParsed;
                                break;
                            }
                        }
                        AddFilterToDictionary(ItemType.TechDisk, techDiskFilter);
                        break;
                    case "r":
                        var regularItemFilter = new RegularItemFilterRule();
                        for (int i = 1; i < tokens.Length; i++)
                        {
                            var namedParam = GetNamedParam(tokens[i].Trim());
                            if (namedParam == null)
                                continue;

                            switch (namedParam.Item1)
                            {
                            case "i":
                                regularItemFilter.ItemName = namedParam.Item2;
                                break;
                            case "c":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                regularItemFilter.Count = descriptorParsed;
                                break;
                            }
                        }
                        AddFilterToDictionary(ItemType.Regular, regularItemFilter);
                        break;
                    case "e":
                        var mesetaItemFilter = new RegularItemFilterRule();
                        for (int i = 1; i < tokens.Length; i++)
                        {
                            var namedParam = GetNamedParam(tokens[i].Trim());
                            if (namedParam == null)
                                continue;

                            switch (namedParam.Item1)
                            {
                            case "c":
                                if (!TryGetNumericDescriptorValue(namedParam.Item2, out descriptorParsed))
                                    continue;

                                mesetaItemFilter.Count = descriptorParsed;
                                break;
                            }
                        }
                        AddFilterToDictionary(ItemType.Meseta, mesetaItemFilter);
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// When successful, Item1 contains the name, Item2 contains the value. Returns 
        /// null on failure.
        /// </summary>
        private Tuple<string, string> GetNamedParam(string token)
        {
            var namedParameter = token.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);

            //Named params must be defined correctly
            if (namedParameter.Length != 2)
                return null;

            return new Tuple<string, string>(namedParameter[0].Trim(), namedParameter[1].Trim());
        }

        private bool TryGetBoolValue(string value, out bool ret)
        {
            return Boolean.TryParse(value, out ret);
        }

        private bool TryGetNumericDescriptorValue(string value, out NumericDescriptor ret)
        {
            ret = null;

            if (value.Length < 1)
                return false;

            var actualNumber = value;
            var comparisonType = NumericFilterComparisonType.Equal;
            if (value.Length >= 2)
            {
                if (value.EndsWith("+"))
                {
                    comparisonType = NumericFilterComparisonType.GreaterThanOrEqual;
                    actualNumber = value.Substring(0, value.Length - 1);
                }
                else if (value.EndsWith("-"))
                {
                    comparisonType = NumericFilterComparisonType.LessThanOrEqual;
                    actualNumber = value.Substring(0, value.Length - 1);
                }
            }

            int intParsed;
            if (!Int32.TryParse(actualNumber, out intParsed))
                return false;

            ret = new NumericDescriptor(intParsed, comparisonType);
            return true;
        }

        private void AddFilterToDictionary(ItemType itemType, FilterRule rule)
        {
            if (!_filterRules.ContainsKey(itemType))
                _filterRules[itemType] = new List<FilterRule>();

            _filterRules[itemType].Add(rule);
        }

        private enum NumericFilterComparisonType { Equal, GreaterThanOrEqual, LessThanOrEqual }

        private class FilterRule
        {
            private string _itemName;

            /// <summary>
            /// Gets or sets the item name to match against. Used by all filter rules except meseta.
            /// </summary>
            public string ItemName
            {
                get { return _itemName; }
                set
                {
                    _itemName = value;
                    DoesNameMatching = true;
                }
            }
            private bool DoesNameMatching { get; set; }

            public virtual bool IsMatch(ItemViewModel item)
            {
                if (DoesNameMatching && !ItemName.Equals(item.ItemName, StringComparison.CurrentCultureIgnoreCase))
                    return false;

                return true;
            }
        }

        private class WeaponFilterRule : FilterRule
        {
            private bool _isTekked;
            public bool IsTekked
            {
                get { return _isTekked; }
                set
                {
                    _isTekked = value;
                    DoesIsTekkedMatching = true;
                }
            }
            private bool DoesIsTekkedMatching { get; set; }

            private bool _isBasic;
            public bool IsBasic
            {
                get { return _isBasic; }
                set
                {
                    _isBasic = value;
                    DoesIsBasicMatching = true;
                }
            }
            private bool DoesIsBasicMatching { get; set; }

            private NumericDescriptor _grind;
            public NumericDescriptor Grind
            {
                get { return _grind; }
                set
                {
                    _grind = value;
                    DoesGrindMatching = true;
                }
            }
            private bool DoesGrindMatching { get; set; }

            private NumericDescriptor _native;
            public NumericDescriptor Native
            {
                get { return _native; }
                set
                {
                    _native = value;
                    DoesNativeMatching = true;
                }
            }
            private bool DoesNativeMatching { get; set; }

            private NumericDescriptor _aBeast;
            public NumericDescriptor ABeast
            {
                get { return _aBeast; }
                set
                {
                    _aBeast = value;
                    DoesABeastMatching = true;
                }
            }
            private bool DoesABeastMatching { get; set; }

            private NumericDescriptor _machine;
            public NumericDescriptor Machine
            {
                get { return _machine; }
                set
                {
                    _machine = value;
                    DoesMachineMatching = true;
                }
            }
            private bool DoesMachineMatching { get; set; }

            private NumericDescriptor _dark;
            public NumericDescriptor Dark
            {
                get { return _dark; }
                set
                {
                    _dark = value;
                    DoesDarkMatching = true;
                }
            }
            private bool DoesDarkMatching { get; set; }

            private NumericDescriptor _hit;
            public NumericDescriptor Hit
            {
                get { return _hit; }
                set
                {
                    _hit = value;
                    DoesHitMatching = true;
                }
            }
            private bool DoesHitMatching { get; set; }

            private WeaponSpecialDescriptor _special;
            public WeaponSpecialDescriptor Special
            {
                get { return _special; }
                set
                {
                    _special = value;
                    DoesSpecialMatching = true;
                }
            }
            private bool DoesSpecialMatching { get; set; }

            public override bool IsMatch(ItemViewModel item)
            {
                var weaponItem = (WeaponViewModel)item;

                if (DoesIsTekkedMatching && weaponItem.IsUntekked == IsTekked)
                    return false;
                else if (DoesIsBasicMatching && weaponItem.IsBasic != IsBasic)
                    return false;
                else if (DoesGrindMatching && !Grind.IsMatch(weaponItem.Grind))
                    return false;
                else if (DoesNativeMatching && !Native.IsMatch(weaponItem.NativeBonus))
                    return false;
                else if (DoesABeastMatching && !ABeast.IsMatch(weaponItem.ABeastBonus))
                    return false;
                else if (DoesMachineMatching && !Machine.IsMatch(weaponItem.MachineBonus))
                    return false;
                else if (DoesDarkMatching && !Dark.IsMatch(weaponItem.DarkBonus))
                    return false;
                else if (DoesHitMatching && !Hit.IsMatch(weaponItem.HitBonus))
                    return false;
                else if (DoesSpecialMatching && !Special.IsMatch(weaponItem.Special))
                    return false;

                return base.IsMatch(weaponItem);
            }
        }

        private class SWeaponFilterRule : FilterRule
        {
            private NumericDescriptor _grind;
            public NumericDescriptor Grind
            {
                get { return _grind; }
                set
                {
                    _grind = value;
                    DoesGrindMatching = true;
                }
            }
            private bool DoesGrindMatching { get; set; }

            private string _customName;
            public string CustomName
            {
                get { return _customName; }
                set
                {
                    _customName = value;
                    DoesCustomNameMatching = true;
                }
            }
            private bool DoesCustomNameMatching { get; set; }

            private WeaponSpecialDescriptor _special;
            public WeaponSpecialDescriptor Special
            {
                get { return _special; }
                set
                {
                    _special = value;
                    DoesSpecialMatching = true;
                }
            }
            private bool DoesSpecialMatching { get; set; }

            public override bool IsMatch(ItemViewModel item)
            {
                var sWeaponItem = (SWeaponViewModel)item;

                if (DoesGrindMatching && !Grind.IsMatch(sWeaponItem.Grind))
                    return false;
                else if (DoesCustomNameMatching && !CustomName.Equals(sWeaponItem.CustomName))
                    return false;
                else if (DoesSpecialMatching && !Special.IsMatch(sWeaponItem.Special))
                    return false;

                return base.IsMatch(sWeaponItem);
            }
        }

        private class ShieldFilterRule : FilterRule
        {
            private NumericDescriptor _defense;
            public NumericDescriptor Defense
            {
                get { return _defense; }
                set
                {
                    _defense = value;
                    DoesDefenseMatching = true;
                }
            }
            private bool DoesDefenseMatching { get; set; }

            private NumericDescriptor _evasion;
            public NumericDescriptor Evasion
            {
                get { return _evasion; }
                set
                {
                    _evasion = value;
                    DoesEvasionMatching = true;
                }
            }
            private bool DoesEvasionMatching { get; set; }

            public override bool IsMatch(ItemViewModel item)
            {
                var shieldItem = (ShieldViewModel)item;

                if (DoesDefenseMatching && !Defense.IsMatch(shieldItem.Defense + shieldItem.DefenseBonus))
                    return false;
                else if (DoesEvasionMatching && !Evasion.IsMatch(shieldItem.Evasion + shieldItem.EvasionBonus))
                    return false;

                return base.IsMatch(shieldItem);
            }
        }

        private class ArmorFilterRule : ShieldFilterRule
        {
            private NumericDescriptor _slots;
            public NumericDescriptor Slots
            {
                get { return _slots; }
                set
                {
                    _slots = value;
                    DoesSlotsMatching = true;
                }
            }
            private bool DoesSlotsMatching { get; set; }

            public override bool IsMatch(ItemViewModel item)
            {
                var armorItem = (ArmorViewModel)item;

                if (DoesSlotsMatching && !Slots.IsMatch(armorItem.Slots))
                    return false;

                return base.IsMatch(armorItem);
            }
        }

        private class TechDiskFilterRule : FilterRule
        {
            private NumericDescriptor _level;
            public NumericDescriptor Level
            {
                get { return _level; }
                set
                {
                    _level = value;
                    DoesLevelMatching = true;
                }
            }
            private bool DoesLevelMatching { get; set; }

            public override bool IsMatch(ItemViewModel item)
            {
                var techDiskItem = (TechDiskViewModel)item;

                if (DoesLevelMatching && !Level.IsMatch(techDiskItem.Level))
                    return false;

                return base.IsMatch(techDiskItem);
            }
        }

        private class RegularItemFilterRule : FilterRule
        {
            private NumericDescriptor _count;
            public NumericDescriptor Count
            {
                get { return _count; }
                set
                {
                    _count = value;
                    DoesCountMatching = true;
                }
            }
            private bool DoesCountMatching { get; set; }

            public override bool IsMatch(ItemViewModel item)
            {
                var regularItem = (RegularItemViewModel)item;

                if (DoesCountMatching && !Count.IsMatch(regularItem.Count))
                    return false;

                return base.IsMatch(regularItem);
            }
        }

        private class NumericDescriptor
        {
            public int Number { get; private set; }
            public NumericFilterComparisonType MatchType { get; private set; }

            public NumericDescriptor(int number) : this(number, NumericFilterComparisonType.Equal) { }
            public NumericDescriptor(int number, NumericFilterComparisonType matchType) 
            {
                Number = number;
                MatchType = matchType;
            }

            public virtual bool IsMatch(int number)
            {
                switch (MatchType)
                {
                case NumericFilterComparisonType.GreaterThanOrEqual:
                    return number >= Number;
                case NumericFilterComparisonType.LessThanOrEqual:
                    return number <= Number;
                default:
                    return number == Number;
                }
            }
        }

        private class WeaponSpecialDescriptor
        {
            public string Special { get; private set; }

            public WeaponSpecialDescriptor(string special)
            {
                Special = special;
            }

            public virtual bool IsMatch(string special)
            {
                switch (special.ToLower())
                {
                case "heat":
                case "fire":
                case "flame":
                case "burning":
                    if (Special == "burning") return true;
                    break;
                case "shock":
                case "thunder":
                case "storm":
                case "tempest":
                    if (Special == "tempest") return true;
                    break;
                case "ice":
                case "frost":
                case "freeze":
                case "blizzard":
                    if (Special == "blizzard") return true;
                    break;
                case "bind":
                case "hold":
                case "sieze":
                case "arrest":
                    if (Special == "arrest") return true;
                    break;
                case "panic":
                case "riot":
                case "havoc":
                case "chaos":
                    if (Special == "chaos") return true;
                    break;
                case "draw":
                case "drain":
                case "fill":
                case "gush":
                    if (Special == "gush") return true;
                    break;
                case "dim":
                case "shadow":
                case "dark":
                case "hell":
                    if (Special == "hell") return true;
                    break;
                case "heart":
                case "mind":
                case "soul":
                case "geist":
                    if (Special == "geist") return true;
                    break;
                case "charge":
                    if (Special == "charge") return true;
                    break;
                case "spirit":
                    if (Special == "spirit") return true;
                    break;
                case "berserk":
                    if (Special == "berserk") return true;
                    break;
                case "master's":
                case "lord's":
                case "king's":
                    if (Special == "king") return true;
                    break;
                case "devil's":
                case "demon's":
                    if (Special == "demon") return true;
                    break;
                case "jellen":
                    if (Special == "jellen") return true;
                    break;
                case "zalure":
                    if (Special == "zalure") return true;
                    break;
                }

                return false;
            }
        }

        #endregion
    }
}
